from datetime import datetime

from InvoiceOCR.Invoice import Invoice
from pywebsite.controller_paddleocr import get_ocr_result
from pywebsite.controller_paddleocr import get_ocr_result_img
from PIL import Image
import numpy as np
from pathlib import Path
import os

from decimal import Decimal
import decimal
import re
import json

import sys
import io
# sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

BASE_DIR = Path(__file__).resolve().parent.parent
PROJECT_ROOT = os.path.join(BASE_DIR, 'pywebsite')
ORC_FONT_ROOT = os.path.join(PROJECT_ROOT, 'fonts')
ORC_MODEL_ROOT = os.path.join(PROJECT_ROOT, 'models')

# DET_MODEL_DIR = os.path.join(ORC_MODEL_ROOT, 'PP-OCRv4_server_det_infer')
REC_MODEL_DIR = os.path.join(ORC_MODEL_ROOT, 'PP-OCRv4_server_rec_doc_infer')

def check_invoice_img(filepath, holder=0.5, debug=False):
    image = Image.open(filepath).convert('RGB')
    result1 = get_ocr_result(np.array(image), rec_model_dir=REC_MODEL_DIR)
    # 生产环境有[0]: result = result1[0]
    result = result1[0]
    width, height = 0, 0
    with Image.open(filepath) as img:
        # 获取图像的分辨率
        width, height = img.size

    # boxes = [line[0] for line in result]  # line[0]=[[x1, y1],[x2,y1],[x1,y2],[x2,y2]]   (x~image width, y~image height)
    boxes, txts, scores = [], [], []

    features = set()
    date_ret = ""
    invoice_code = None
    max_amount = 0  # 右下角$金额,普通发票
    max_amount_heji_pos_y = None  # 价税合计 pos
    max_amount_heji = None  # 价税合计 $金额,最高优先
    last_amount_txt = None  # 最后一个 $金额 防止价税合计识别顺序靠后 amount,index
    code_8 = None
    code_10 = None
    code_12 = None
    code_18 = None

    max_amount_train_pos_y = None  # 票价 pos
    max_amount_train = 0  # $金额,火车票用(Y可能被识别为$)

    heji_pos_y = None  # "合计"位置
    heji_ji_index = None  # "计" index
    heji_he_pos = None  # "合"字位置  pos,index
    tax_amount_alter = None  # $税合计金额时,备用计算值 amount,pos,index
    tax_amount = 0  # 税额合计值
    tax_amount_col = 0  # 税额位置数值
    tax_amount_pos_x = None  # 税额合计值位置
    tax_rate = None  # 税率比
    tax_rate_index = None  # 税率比index
    tax_rate_pos_x = None  # "税率" 位置
    tax_rate_pos_y_confirm = None  # 行项目税额值位置,可能多行
    tax_rate_pair = []  # 税率和税额值行项目

    tax_amount_1_pos_x = None
    tax_amount_1_index = None

    txt_amount = 0

    index_dollar = -1

    # 根据最下方文字位置,裁剪保留10%空白
    # 最下方: result[len(result)-1][0][3]  最后一个可能在左边,但是在最下面
    txt_top = 0
    txt_bottom = 0
    if result and len(result) > 0:
        # 最高
        t_x, t_y = result[0][0][0]
        txt_top = t_y
        # 最低
        b_x, b_y = result[len(result)-1][0][3]
        txt_bottom = b_y

    for index, line in enumerate(result):
        # print(line[0],type(line[0]))
        txt = line[1][0]
        txt = txt.replace(" ", "")
        # 处理¥和金额被分开识别,但是一般会连在一起
        if txt.endswith("￥") or txt.endswith("¥") or txt.endswith("Y"):
            index_dollar = index
        if index == index_dollar+1:
            txt = "¥"+txt
            index_dollar = -1
        loc, pos = determine_box_position(line[0], width, height, txt_top, txt_bottom)
        new_features, amount, date_obj, eight_digit_number, ten_digit_number, twelve_digit_number, more_than_fifteen_digit_number  = get_text_features(txt, loc, pos)


        if index == 35:
            a = 1
            pass

        if amount:
            last_amount_txt = amount,index
        # 价税合计 顺序倒置,同行金额,最高优先
        if last_amount_txt and "价税合计" in txt and abs(last_amount_txt[1]-index) < 3:
            if max_amount_heji is None or last_amount_txt[0] > max_amount_heji:
                max_amount_heji = last_amount_txt[0]
        # 价税合计同行金额,最高优先
        if amount and max_amount_heji_pos_y and abs(max_amount_heji_pos_y - pos[1]) < 0.06:
            if max_amount_heji is None or amount > max_amount_heji:
                max_amount_heji = amount

        # 火车发票
        if last_amount_txt and "票价" in txt and abs(last_amount_txt[1]-index) < 3:
            max_amount_train = last_amount_txt[0]
        # 价税合计同行金额,最高优先
        if amount and max_amount_train_pos_y and abs(max_amount_train_pos_y - pos[1]) < 0.05:
            max_amount_train = amount

        # 金额
        if amount and amount > max_amount and loc in ["bottom_right"]:
            max_amount = amount
        if date_obj:
            date_ret = datetime.strftime(date_obj, '%Y-%m-%d')
        if eight_digit_number:
            code_8 = eight_digit_number
        if ten_digit_number:
            code_10 = ten_digit_number
        if twelve_digit_number:
            code_12 = twelve_digit_number
        if more_than_fifteen_digit_number:
            code_18 = more_than_fifteen_digit_number

        if pos[0] < 0.4 and str.strip(txt) == "合":
            heji_he_pos = pos, index
        if pos[0] < 0.4 and str.strip(txt) == "计":
            heji_ji_index = index
        if pos[0] < 0.4 and str.strip(txt) == "计" and heji_he_pos:  # 位置靠左,合计两个字在同一水平且距离很近
            if abs(heji_he_pos[0][1]-pos[1]) < 0.05 and abs(heji_he_pos[1]-index) < 2:
                heji_pos_y = pos[1]
        if str.strip(txt) == "合计":
            heji_pos_y = pos[1]
        if "税" in str.strip(txt) and len(txt) < 3:
            tax_amount_1_pos_x = pos[0]
            tax_amount_1_index = index
        if "额" in str.strip(txt) and len(txt) < 3 and tax_amount_1_pos_x and abs(tax_amount_1_index - index) < 3:
            tax_amount_pos_x = (pos[0]+tax_amount_1_pos_x)/2
        if "税额" in txt:
            tax_amount_pos_x = pos[0]
        if "税率" in txt:
            tax_rate_pos_x = pos[0]
        if "价税合计" in txt:
            max_amount_heji_pos_y = pos[1]
        if "票价" in txt:
            max_amount_train_pos_y = pos[1]

        tax_rate_pattern = r'^\d+(\.\d+)?%$'  # 3% 百分数
        if re.match(tax_rate_pattern, txt) and tax_rate_pos_x and abs(tax_rate_pos_x-pos[0]) < 0.1:
            tax_rate_pos_y_confirm = pos[1]
            tax_rate, tax_rate_index = txt, index
        if tax_rate and abs(tax_rate_index-index) < 3 and tax_rate_pos_y_confirm and tax_amount_pos_x and abs(tax_amount_pos_x - pos[0] < 0.1) and abs(tax_rate_pos_y_confirm - pos[1] < 0.05):  # 税额行项目值,与税率比index不远 ,有税率数位置和税额在同一垂线上,和tax_rate在同一水平上
            try:
                tax_rate_value = float(txt)  # 尝试将字符串转换为浮点数
                tax_rate_pair.append((tax_rate, tax_rate_value))
                tax_rate = None
            except ValueError:
                pass

        # 税额合计辅助识别,只识别到合或者计,在偏中间的位置,也算
        if amount and tax_amount_pos_x and abs(tax_amount_pos_x - pos[0] < 0.05) and (pos[1] > 0.3 and pos[1] < 0.7):
            tax_amount_alter = amount, pos, index
        # 有合计位置,有金额且与"税额"共垂直线,与"合计"共水平线
        if heji_pos_y and tax_amount_pos_x and amount and amount > 0 and abs(tax_amount_pos_x - pos[0] < 0.05) and abs(heji_pos_y-pos[1]) < 0.05:
            tax_amount = amount
            heji_pos_y = None  # 防止下一行太近误判

        # 有合计位置,有数值,与"合计"共水平线 # 税额位置没有$符号的独立数值
        if tax_rate is None and heji_pos_y and tax_amount_pos_x and abs(tax_amount_pos_x - pos[0] < 0.05) and abs(heji_pos_y-pos[1]) < 0.05:
            try:
                tax_rate_value = float(txt)  # 尝试将字符串转换为浮点数
                tax_amount_col = tax_rate_value
            except ValueError:
                pass

        # 中文金额
        chinese_pattern = r'[零壹贰叁肆伍陆柒捌玖拾佰仟万亿圆园元角分整参]+'  # 参 为可能错误的识别
        chinese_match = re.search(chinese_pattern, txt)
        if chinese_match:
            chinese_amount_str = chinese_match.group(0)
            if len(chinese_amount_str) > 2:
                rmb_value = rmb_to_number(chinese_amount_str)
                if rmb_value > txt_amount:
                    txt_amount = rmb_value

        features = features | new_features

        txts.append(loc+": "+txt+";index:"+str(index)+f';pos:({pos[0]:.3f},{pos[1]:.3f})')  # 提取 txts
        boxes.append(line[0])  # 提取 boxes
        score = line[1][1]
        scores.append(score)  # 提取 scores

    #  获取发票类型,0为数电发票,1为电子发票,2为机打发票
    invoice_type = get_invoice_type(features)
    if invoice_type == 0:
        invoice_code = code_18
    elif invoice_type == 1:
        if code_12 is not None:
            invoice_code = code_8 + "-" + code_12
        if code_10 is not None:
            invoice_code = code_8 + "-" + code_10
    elif invoice_type == 2:
        if code_12 is not None:
            invoice_code = code_8 + "-" + code_12
        if code_10 is not None:
            invoice_code = code_8 + "-" + code_10
    elif invoice_type == 11:
        invoice_code = code_18
        if max_amount_train > 0:
            max_amount = max_amount_train

    # 税率组中有3%或9%
    if len(tax_rate_pair) > 0 and any(pair[0] == "9%" or pair[0] == "3%" for pair in tax_rate_pair):
        features.add(100)
    # 未识别到税合计时,计算合计
    if tax_amount == 0 and tax_amount_alter and ((heji_he_pos and abs(heji_he_pos[1]-tax_amount_alter[2]) < 3) or (heji_ji_index and abs(heji_ji_index-tax_amount_alter[2]) < 5)):  # 税额合计辅助识别,只识别到合或者计,也算 右侧可能有多余字符,增加到5
        tax_amount = tax_amount_alter[0]
    # 税额列最后的数值
    if tax_amount == 0 and tax_amount_col and tax_amount_col > 0:
        tax_amount = tax_amount_col
    # 合计未识别到税额时,另外计算
    if len(tax_rate_pair) > 0 and tax_amount == 0:
        for pair in tax_rate_pair:
            tax_amount += pair[1]

    # 价税合计最高优先
    if max_amount_heji:
        max_amount = max_amount_heji
        if tax_amount == max_amount:  # 价税合计处合计金额可能因为文件偏斜错误识别为税额的合计
            max_amount = txt_amount

    ret_img = get_ocr_result_img(image, boxes, txts, scores)
    if invoice_type > 0:
        return Invoice(invoice_type, max_amount, invoice_code, date_ret, tax_rate_pair, tax_amount, txt_amount, sorted(features)), ret_img
    return Invoice(invoice_type, max_amount, invoice_code, date_ret, tax_rate_pair, tax_amount, txt_amount, sorted(features)), ret_img


def extract_amount(text):
    # 正则表达式匹配以“￥”开头，后面跟着可选的空格，接着是一个或多个数字（包括小数点）
    pattern = r'[￥¥Y]\s*([\d,]+(?:\.\d{1,2})?)'
    pattern2 = r'.*小写.*([\d,]+(?:\.\d{1,2})?)'
    # pattern3 = r'.*\(.*\).*([\d,]+(?:\.\d{1,2})?)'

    pattern_amount = r'\d+\.?\d*'

    match = re.search(pattern, text)
    match2 = re.search(pattern2, text)
    if match or match2:
        amounts = re.findall(pattern_amount, text)
        amount = max([abs(float(num)) for num in amounts])
        return amount
    return None


def determine_box_position(box, width, height, txt_top, txt_bottom):
    # 左上,右下坐标
    x1, y1 = box[0]
    x2, y2 = box[3]

    # 清除底部过多空白
    height = min(height, txt_bottom*1.1)

    # 顶部超过10%空白,则减为10%
    content_height = txt_bottom - txt_top
    header = min(content_height*0.1, txt_top)
    if txt_top > header:
        cut_top = txt_top - header
        y1 -= cut_top
        y2 -= cut_top
        height -= cut_top

    # 计算框的中心点
    center_x = (x1 + x2) / 2
    center_y = (y1 + y2) / 2

    # 计算图片的中心点 数据可能偏左
    left_x = width * 3 / 7
    right_x = width * 4 / 7
    top_y = height * 2 / 8 #高于1/4处
    bottom_y = height * 4 / 7 #低于4/7处

    # 判断方位
    if center_x < left_x and center_y < top_y:
        return "top_left", (center_x/width, center_y/height)
    elif center_x >= right_x and center_y < top_y:
        return "top_right", (center_x/width, center_y/height)
    elif center_x < left_x and center_y >= bottom_y:
        return "bottom_left", (center_x/width, center_y/height)
    elif center_x >= right_x and center_y >= bottom_y:
        return "bottom_right", (center_x/width, center_y/height)
    else:
        if center_x < left_x:
            return "center_left", (center_x/width, center_y/height)
        elif center_x >= right_x:
            return "center_right", (center_x/width, center_y/height)
        return "center", (center_x/width, center_y/height)


# 字符串容错处理 在text找到target韩游的字符串,超过n个匹配(不同字符)才行
def check_similarity(text, target, n=3):
    # 将目标字符串拆分为字符
    target_chars = set(target)  # 使用集合来去重
    matched_chars = set()  # 存储匹配的字符

    # 遍历目标字符，检查是否在文本中
    for char in target_chars:
        if char in text:
            matched_chars.add(char)

    # 判断是否符合至少3个字匹配
    return len(matched_chars) >= n

# 特征提取函数
def get_text_features(intext, loc, pos):
    """
   @数电 4,5,11,12,!10,!14,6,!7,!8,19,!3, (9|1|13|20)
   @电子 !3,4,5,10,11,12,7,!6,8,15,16(9|1|13|20)
   @机打 3,4,5,7,8,12,!14,15,16
   1.上方包含"用发票" (专用)
   2.左上角10位数,为@机打发票号码
   3.右上角包含"No",8位数字
   4.右上角包含日期,格式为"YYYY年MM月DD日"
   5.有最大金额
   6.左下角有"开票人"
   7.左下角有"收款人"
   8.右下角有"销售方"
   9.上方包含"电子发"
   10.右上角有"发票代码"
   11.右上角有"发票号码"
   12.右上角有"开票日期"
   13.上方包含"通发票" (普通)
   14.左上角有"机器编号"
   15.左下角有"复核"
   16.右上角有8位数字,为发票号码
   17.右上角有10位数字,为发票代码
   18.右上角有12位数字,为@电子@机打发票代码
   19.右上角有数字大于15位,(18)为@数电发票号码
   20.上方包含"增值税"
   21.右下方包含"专用章"
   22.左-中上包含"运输服务"
   23.左上角有"发票号码"
   24.左上角有数字大于18位,(20)为火车电子发票
   25.包含"铁路"
   26.左上方包含"成品油"
   27.左上方包含"建筑服务"
   28.下方右侧有"开票人"
   100.税率有3%或9%
   """
    features = set()
    date_txt, date_obj, eight_digit_number, ten_digit_number, twelve_digit_number, eighteen_digit_number = None, None, None, None, None, None

    # 定义特征匹配的正则表达式

    date_pattern = re.compile(
        r'(\d{4}[\u4e00-\u9fa5]\d{2}月\d{2}日)|'  # 替代年
        r'(\d{4}年\d{2}[\u4e00-\u9fa5]\d{2}日)|'  # 替代月
        r'(\d{4}年\d{2}月\d{2}[\u4e00-\u9fa5])'   # 替代日
    )
    ten_machine_number = re.compile(r'(?<!\d)\d{10}(?!\d)')
    eight_digit_number_pattern = re.compile(r'(?<!\d)\d{8}(?!\d)')
    twelve_digit_number_pattern = re.compile(r'(?<!\d)\d{12}(?!\d)')
    more_than_eighteen_digits_pattern = re.compile(r'(?<!\d)\d{18,}(?!\d)')

    amount = extract_amount(intext)
    if amount:
        features.add(5)
    text = extract_chinese_and_numbers(intext)

    if "铁路" in text:
        features.add(25)

    if "运输服务" in text and pos[0] < 0.5 and pos[1] < 0.6:
        features.add(22)

    if pos[1] < 0.3:
        if "用发票" in text:
            features.add(1)
        if "电子发" in text:
            features.add(9)
        if "通发票" in text:
            features.add(13)
        if "增值税" in text:
            features.add(20)

    if pos[0] > 0.5 and pos[1] > 0.5 and re.search(r'开.人', text):
        features.add(28)

    # 根据位置判断特征
    if loc in ["top_left", "top_right", "bottom_left", "bottom_right"]:
        if loc == "top_left":
            ten_digit_match = ten_machine_number.search(text)
            if ten_digit_match:
                features.add(2)
                ten_digit_number = ten_digit_match.group()
            if "机器编号" in text:
                features.add(14)
            if "成品油" in text:
                features.add(26)
            if "建筑服务" in text:
                features.add(27)
            if check_similarity(text, "发票号码", n=3):
                features.add(23)
            more_than_fifteen_digit_match = more_than_eighteen_digits_pattern.search(text)
            if more_than_fifteen_digit_match:
                features.add(24)
                eighteen_digit_number = more_than_fifteen_digit_match.group()

        if loc == "top_right":
            if "No" in text:
                eight_digit_match = eight_digit_number_pattern.search(text)
                if eight_digit_match:
                    features.add(3)
                    eight_digit_number = eight_digit_match.group()

            date_match = date_pattern.search(text)
            if date_match:
                date_txt = date_match.group()
                date_parts_pattern = re.compile(r'(\d{4})[\u4e00-\u9fa5](\d{2})[\u4e00-\u9fa5](\d{2})')
                match = date_parts_pattern.match(date_txt)
                if match:
                    features.add(4)
                    year, month, day = match.groups()
                    date_obj = datetime(year=int(year), month=int(month), day=int(day))

            if check_similarity(text, "发票代码", n=3):
                features.add(10)
            if check_similarity(text, "发票号码", n=3):
                features.add(11)
            if check_similarity(text, "开票日期", n=3):
                features.add(12)

            eight_digit_match = eight_digit_number_pattern.search(text)
            if eight_digit_match:
                features.add(16)
                eight_digit_number = eight_digit_match.group()

            ten_digit_match = ten_machine_number.search(text)
            if ten_digit_match:
                features.add(17)
                ten_digit_number = ten_digit_match.group()

            twelve_digit_match = twelve_digit_number_pattern.search(text)
            if twelve_digit_match:
                features.add(18)
                twelve_digit_number = twelve_digit_match.group()

            more_than_fifteen_digit_match = more_than_eighteen_digits_pattern.search(text)
            if more_than_fifteen_digit_match:
                features.add(19)
                eighteen_digit_number = more_than_fifteen_digit_match.group()

        if loc == "bottom_left":
            if re.search(r'开.人', text):
                features.add(6)
            if "收款人" in text:
                features.add(7)
            if "复核" in text:
                features.add(15)

        if loc == "bottom_right":
            if "销售方" in text:
                features.add(8)
            if "专用章" in text:
                features.add(21)

    # print(intext.encode('gbk', errors='replace').decode('gbk'), features, amount, date_txt, eight_digit_number, ten_digit_number, twelve_digit_number, eighteen_digit_number)
    # 返回特征、日期和数值信息
    return features, amount, date_obj, eight_digit_number, ten_digit_number, twelve_digit_number, eighteen_digit_number


def extract_chinese_and_numbers(input_string):
    # 正则表达式: [\u4e00-\u9fa5] 匹配汉字, [0-9] 匹配数字, [a-zA-Z] 匹配英文字母
    pattern = r'[\u4e00-\u9fa5a-zA-Z0-9]'
    # 使用 re.findall() 提取所有匹配的字符
    matches = re.findall(pattern, input_string)
    # 将匹配的字符连接成一个字符串
    result = ''.join(matches)
    return result

def rmb_to_number(rmb_str):
    rmb_str = rmb_str.replace("参", "叁")
    rmb_str = rmb_str.replace("园", "圆")
    try:
        rmb_dict = {
            '壹': 1, '贰': 2, '叁': 3, '肆': 4, '伍': 5, '陆': 6, '柒': 7, '捌': 8, '玖': 9,
            '拾': 10, '佰': 100, '仟': 1000, '万': 10000, '亿': 100000000,
            '零': 0, '元': 'yuan', '角': 0.1, '分': 0.01, '整': 0, '圆': 'yuan'
        }

        total = 0.0
        current_section = 0  # 处理万/亿前的当前段累积值
        current_value = 0    # 当前单位组内的累积值（如几千几百几十）
        num = 0              # 当前未处理的数字
        decimal_part = 0.0
        in_decimal = False   # 是否进入小数部分（遇到元/圆后为True）

        # 预处理：移除空格和'整'
        rmb_str = rmb_str.replace(' ', '').replace('整', '')

        for char in rmb_str:
            if char not in rmb_dict:
                continue
            value = rmb_dict[char]

            # 处理元/圆：整数部分结束
            if char in ('元', '圆'):
                current_value += num
                current_section += current_value
                total += current_section
                current_section, current_value, num = 0, 0, 0
                in_decimal = True
            # 小数部分处理
            elif in_decimal:
                if char in ('角', '分'):
                    decimal_part += num * value
                    num = 0
                elif char == '零':
                    pass  # 小数部分无需处理零
                else:
                    num = value  # 小数部分的数字
            # 整数部分处理
            else:
                if char in ('万', '亿'):
                    current_value += num
                    current_section += current_value * value
                    current_value, num = 0, 0
                elif char in ('拾', '佰', '仟'):
                    current_value += num * value
                    num = 0
                elif char == '零':
                    pass  # 零不影响累积，仅分隔
                else:
                    num = value  # 更新当前数字

        # 处理剩余未结算的整数部分
        if not in_decimal:
            current_value += num
            current_section += current_value
            total += current_section

        # 加上小数部分
        total += decimal_part
    except Exception as e:
        return 0
    # 返回整数或浮点数
    return total

def get_invoice_type(features):
    # @数电 4,5,12,!14,!8,19,!3 (9|1|13|20)   去掉了11 6
    if 4 in features and 5 in features and 12 in features and 14 not in features and 8 not in features and 19 in features and 3 not in features and (9 in features or 1 in features or 13 in features or 20 in features):
        return 0

    # @电子 !3,4,5,!6,7,(8|21),1012,15,16(9|1|13|20|(7&8&15))    去掉了11
    if 3 not in features and 4 in features and 5 in features and 10 in features and 12 in features and 7 in features and 6 not in features and (8 in features or 21 in features) and 15 in features and 16 in features and (9 in features or 1 in features or 13 in features or 20 in features or (7 in features and 8 in features and 15 in features)):
        return 1

    # @机打 3,4,5,7,(8|21|28),12,!14,15,16, (9|1|13|20|(7&8&15))
    if 3 in features and 4 in features and 5 in features and 7 in features and (8 in features or 21 in features or 28 in features) and 12 in features and 14 not in features and 15 in features and 16 in features and (9 in features or 1 in features or 13 in features or 20 in features or (7 in features and 8 in features and 15 in features)):
        return 2
    # 火车电子发票 4,5,23,24,25,(9|12)
    if 4 in features and 5 in features and 23 in features and 24 in features and 25 in features and (9 in features or 12 in features):
        return 11

    return -1
